export const TokenizerState = {
  Static: "Static",
  Param: "Param",
  ParamRegExp: "ParamRegExp",
  ParamRegExpEnd: "ParamRegExpEnd",
  EscapeNext: "EscapeNext",
};

export const TokenType = {
  Static: "Static",
  Param: "Param",
  Group: "Group",
};

const VALID_PARAM_RE = /[a-zA-Z0-9_]/;

const ROOT_TOKEN = {
  type: TokenType.Static,
  value: "",
};

export function tokenizePath(path) {
  if (!path) return [[]];
  if (path === "/") return [[ROOT_TOKEN]];
  if (!path.startsWith("/")) {
    throw new Error(`Invalid path "${path}"`); // 路径必须包含前缀斜杠开头
  }

  function crash(message) {
    throw new Error(`ERR (${state})/"${buffer}": ${message}`);
  }

  let state = TokenizerState.Static;
  let previousState = state;
  const tokens = [];

  let segment;

  function finalizeSegment() {
    if (segment) tokens.push(segment);
    segment = [];
  }

  // index on the path
  let i = 0;
  // char at index
  let char;
  // buffer of the value read
  let buffer = "";
  // custom regexp for a param
  let customRe = "";

  function consumeBuffer() {
    if (!buffer) return;

    if (state === TokenizerState.Static) {
      segment.push({
        type: TokenType.Static,
        value: buffer,
      });
    } else if (
      state === TokenizerState.Param ||
      state === TokenizerState.ParamRegExp ||
      state === TokenizerState.ParamRegExpEnd
    ) {
      if (segment.length > 1 && (char === "*" || char === "+"))
        crash(
          `A repeatable param (${buffer}) must be alone in its segment. eg: '/:ids+.`
        );
      segment.push({
        type: TokenType.Param,
        value: buffer,
        regexp: customRe,
        repeatable: char === "*" || char === "+",
        optional: char === "*" || char === "?",
      });
    } else {
      crash("Invalid state to consume buffer");
    }
    buffer = "";
  }

  function addCharToBuffer() {
    buffer += char;
  }

  while (i < path.length) {
    char = path[i++];

    if (char === "\\" && state !== TokenizerState.ParamRegExp) {
      previousState = state;
      state = TokenizerState.EscapeNext;
      continue;
    }

    switch (state) {
      case TokenizerState.Static:
        if (char === "/") {
          if (buffer) {
            consumeBuffer();
          }
          finalizeSegment();
        } else if (char === ":") {
          consumeBuffer();
          state = TokenizerState.Param;
        } else {
          addCharToBuffer();
        }
        break;

      case TokenizerState.EscapeNext:
        addCharToBuffer();
        state = previousState;
        break;

      case TokenizerState.Param:
        if (char === "(") {
          state = TokenizerState.ParamRegExp;
        } else if (VALID_PARAM_RE.test(char)) {
          addCharToBuffer();
        } else {
          consumeBuffer();
          state = TokenizerState.Static;
          // go back one character if we were not modifying
          if (char !== "*" && char !== "?" && char !== "+") i--;
        }
        break;

      case TokenizerState.ParamRegExp:
        if (char === ")") {
          if (customRe[customRe.length - 1] == "\\")
            customRe = customRe.slice(0, -1) + char;
          else state = TokenizerState.ParamRegExpEnd;
        } else {
          customRe += char;
        }
        break;
      case TokenizerState.ParamRegExpEnd:
        consumeBuffer();
        state = TokenizerState.Static;
        if (char !== "*" && char !== "?" && char !== "+") i--;
        customRe = "";
        break;
      default:
        crash("Unknown state");
        break;
    }
  }

  if (state === TokenizerState.ParamRegExp)
    crash(`Unfinished custom RegExp for param "${buffer}"`);

  consumeBuffer();
  finalizeSegment();

  return tokens;
}
